package org.fjsei.yewu.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import jakarta.servlet.http.Cookie;
import java.io.IOException;
import java.util.Arrays;

public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(JwtTokenAuthenticationFilter.class);
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String NAME_ATTRIBUTE = JwtTokenAuthenticationFilter.class.getName() + ".name";

    private final JwtTokenProvider jwtTokenProvider;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public JwtTokenAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    private boolean isGraphQLRequest(HttpServletRequest request) {
        String servletPath = request.getServletPath();
        String contentType = request.getContentType();
        return "/graphql".equals(servletPath) ||
                (contentType != null && contentType.contains("application/json") &&
                        "POST".equalsIgnoreCase(request.getMethod()));
    }

    private String getRequestBody(HttpServletRequest request) {
        try (BufferedReader reader = request.getReader()) {
            return reader.lines().collect(Collectors.joining("\n"));
        } catch (IOException e) {
            logger.warn("Failed to read request body: {}", e.getMessage());
            return null;
        }
    }

    private GraphQLRequestInfo parseGraphQLRequest(HttpServletRequest request) {
        try {
            String body = getRequestBody(request);
            if (body == null || body.trim().isEmpty()) {
                return null;
            }

            JsonNode jsonNode = objectMapper.readTree(body);

            String query = jsonNode.has("query") ? jsonNode.get("query").asText() : "";
            String operationName = jsonNode.has("operationName") && !jsonNode.get("operationName").isNull()
                    ? jsonNode.get("operationName").asText() : "";
            JsonNode variables = jsonNode.has("variables") ? jsonNode.get("variables") : null;

            // 如果没有operationName，尝试从query中解析
            if (operationName.isEmpty() && query.contains("RefreshToken")) {
                operationName = "RefreshToken";
            }

            // 记录GraphQL请求信息
            logger.info("GraphQL Request - Operation: {}, Query: {}, Variables: {}",
                    operationName.isEmpty() ? "Unknown" : operationName,
                    query.replaceAll("\\s+", " ").trim(),
                    variables != null ? variables.toString() : "{}");

            return new GraphQLRequestInfo(operationName,
                    new CachedBodyHttpServletRequest(request, body.getBytes(StandardCharsets.UTF_8)));

        } catch (Exception e) {
            logger.warn("Failed to parse GraphQL request: {}", e.getMessage());
            return null;
        }
    }

    private static class GraphQLRequestInfo {
        private final String operationName;
        private final HttpServletRequest wrappedRequest;

        public GraphQLRequestInfo(String operationName, HttpServletRequest wrappedRequest) {
            this.operationName = operationName;
            this.wrappedRequest = wrappedRequest;
        }

        public String getOperationName() {
            return operationName;
        }

        public HttpServletRequest getWrappedRequest() {
            return wrappedRequest;
        }
    }

    //boot3.3.2新版本的：替换掉旧的JwtAuthorizationTokenFilter
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        Boolean jwtChecked = (Boolean) request.getAttribute(NAME_ATTRIBUTE);
        if (null != jwtChecked && jwtChecked) {
            filterChain.doFilter(request, response);
            return;       //已经执行过目标graphQL接口函数了，？ 为何再一次运行到这里。 REST不会两次跑这里。
        } else {
            //避免两次执行下面的代码。针对graphQL异步执行才需要。
            request.setAttribute(NAME_ATTRIBUTE, Boolean.TRUE);
        }
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String originHeads = httpServletRequest.getHeader("Origin");
        String servletPath = httpServletRequest.getServletPath();
        String method = httpServletRequest.getMethod();

        if ("GET".equals(method) && "/forbidden".equals(servletPath)) {
            //response.setContentType("text/html;charset=utf-8");  getMethod()
            if (originHeads.equals(jwtTokenProvider.getServerURI())) {
                //本机端口影射到外网后的，＋从外网访问＋，防火墙主动发起的，和浏览器毫无关系的。.心跳吗？  ?端口影射心跳
                return;
            } else {
                httpServletResponse.sendError(404, "资源不存在");
                return;     //禁止使用的URL
            }
        }

        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("Connection", "keep-alive");
        //response.setHeader("Vary", "Origin");     ??重复了

        GraphQLRequestInfo graphQLInfo = null;
        if (isGraphQLRequest(httpServletRequest)) {
            graphQLInfo = parseGraphQLRequest(httpServletRequest);
            if (graphQLInfo != null && "RefreshToken".equals(graphQLInfo.getOperationName())) {
                logger.info("RefreshToken operation detected, skipping JWT validation");
                filterChain.doFilter(graphQLInfo.getWrappedRequest(), response);
                return;
            }
        }

        final String requestHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);       //取名字是Authorization的参数；
        String authToken = null;
        //注销时：OPTIONS过后requestHeader="";    ?Bearer 和token= 不是同一个参数。
        //?标准没Bearer ;
        //新版graphiQL的客户端都会给服务器Authorization: Bearer ? + cockie=俩个token都带来
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            //浏览器上人工添加Authorization token来做验证权限的场景走这个分支
            authToken = requestHeader.substring(7);
        } else {    //没有Authorization: Bearer开头;
            //  Cookie[] cookies = httpServletRequest.getCookies();
            //不再支持Cookie tokenCook = Arrays.stream(cookies).filter(cookie -> cookie.getName().equals("token")).findFirst().orElse(null);
        }
        if (graphQLInfo != null) {
            httpServletRequest = graphQLInfo.getWrappedRequest();
        }
        //尝试建立用户身份权限信息注册设置到spring security层。但是Session .STATELESS每一次请求包都需要再次设置身份信息。
        boolean passed = jwtTokenProvider.continuedTokenLifeAuthentication(authToken, httpServletRequest, httpServletResponse);
        if (passed)    filterChain.doFilter(httpServletRequest, response);
        //就算Url没有要求授权验证它也可能会到这里。
    }

    private static class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
        private final byte[] cachedBody;

        public CachedBodyHttpServletRequest(HttpServletRequest request, byte[] cachedBody) {
            super(request);
            this.cachedBody = cachedBody;
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            return new CachedBodyServletInputStream(this.cachedBody);
        }

        @Override
        public BufferedReader getReader() throws IOException {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
            return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8));
        }
    }

    private static class CachedBodyServletInputStream extends ServletInputStream {
        private final InputStream cachedBodyInputStream;

        public CachedBodyServletInputStream(byte[] cachedBody) {
            this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public boolean isFinished() {
            try {
                return cachedBodyInputStream.available() == 0;
            } catch (IOException e) {
                return false;
            }
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(jakarta.servlet.ReadListener readListener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read() throws IOException {
            return cachedBodyInputStream.read();
        }
    }
}
